<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>1.4-模板字面量</title>
</head>
<body>
<!--
    JavaScript 中的字符串相比其它语言有着太多的限制。例如，在 ECMAScript 6 之前本章介绍过的字符串的所有新方法都不能使用，而且字符串的拼接方式过于简陋。
    为了能让开发者解决更复杂的问题，ECMAScript 6 中的模板字面量提供了创建领域特定语言（domain-specific languages, DSLs）的语法使其相比
    ECMAScript 5 或更早的版本能更安全的操作相应的内容（领域特定语言面向且专注于的是某单一特定目标的计算机程序设计语言，与通用目的语言如 JavaScript 相反）。
    ECMAScript wiki 提供了 template literal strawman 的如下描述：

    本方案通过语法糖扩展了 ECMAScript 的语法并允许库提供 DSLs 以便产生，查询并操作其它语言的相关内容且对 XSS，SQL 注入等攻击免疫或具有抗性。



    不过实际上，模板字面量是 ECMAScript 6 针对 JavaScript 直到 ECMAScript 5 依然缺失的如下功能的回应：

    - 多行字符串 针对多行字符串的形式概念（formal concept）。
    - 基本的字符串格式化 将字符串中的变量置换为值的能力。
    - 转义 HTML 能将字符串进行转义并使其安全地插入到 HTML 的能力。
    模板字面量以一种全新的表现形式解决了这些问题而不需要向 JavaScript 已有的字符串添加额外的功能。
-->

</body>
<script>
    //=============================1.4.1. 基本语法（Basic Syntax）=============================
    console.group('------------------1.4.1. 基本语法（Basic Syntax）-------------------');
    /*
        简言之，模板字面量由反引号（`）而非一般字符串使用的单或双引号囊括。考虑如下的例子：
     */
    let message = `Hello world!`;

    console.log(message);               // "Hello world!"
    console.log(typeof message);        // "string"
    console.log(message.length);        // 12
    // 该段代码证实 message 变量包含的是一个普通的 JavaScript 字符串。模板字面量语法创建了一个字符串并将其赋值给 message 变量。
    // 如果你想在字符串中包含反引号，只需使用反斜杠（\）转义即可，如下所示：

    let msg = `\`Hello\` world!`;

    console.log(msg);               // "`Hello` world!"
    console.log(typeof msg);        // "string"
    console.log(msg.length);        // 14
    console.groupEnd();

    //=============================1.4.2. 多行字符串（Multiline Strings）========================
    /*
        ECMAScript 6 之前的解决方案（Pre-ECMAScript 6 Workarounds）
        感谢一个长久存在的 bug，JavaScript 的确有一种解决方案。你可以在新行之前放置反斜杠（\）来创建多行字符串。如下所示：

        var message = "Multiline \
        string";

        console.log(message);       // "Multiline string"
        输出的 message 字符串不包含新行，因为反斜杠被视作续延（continuation）信号。为了在输出中包含新行，你需要手动添加它：

        var message = "Multiline \n\
        string";

        console.log(message);       // "Multiline
                                    //  string"
        在所有主流的 JavaScript 引擎中这应该会输出两行，但是该行为被定义为一个 bug 而且许多开发者都建议避免使用这种形式。

        其它的解决方案一般依赖于数组或字符串拼接，例如：

        var message = [
            "Multiline ",
            "string"
        ].join("\n");

        let message = "Multiline \n" +
            "string";
        针对 JavaScript 缺乏的多行字符串特性，开发者给出的所有解决方案都存在某些瑕疵。
     */

    /**+++++++++++++++++++++++++++++++++++++++++++++++++++++*
     *多行字符串的简单使用方式（Multiline Strings the Easy Way)*
     *++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
    // ECMAScript 6 的模板字面量使多行字符串的创建更容易，因为它不需要特殊的语法。只需在想要的位置包含新行即可，而且输出结果也会包含它。例如：

    console.group('-------------------多行字符串的简单使用方式（Multiline Strings the Easy Way)--------------');
    let mess = `Multiline
    string`;

    console.log(mess);           // "Multiline
    //  string"
    console.log(mess.length);    // 16

    console.groupEnd();
    /*
    反引号中的所有空白符都是字符串的一部分，使用缩进要小心。例如：

    let message = `Multiline
                   string`;

    console.log(message);           // "Multiline
                                    //                 string"
    console.log(message.length);    // 31
     */
    console.group('------------------文本分行缩进-----------------------------');
    // 如果将文本分行缩进对你来讲很重要，请考虑将多行模板字面量的第一行空置并在第二行开始缩进，如下所示：
    let html = `
<div>
    <h1>Title</h1>
</div>`.trim();
    // 该段代码在第一行开始创建模板字面量但是在第二行之前并没有包含任何字符。HTML 标签的缩进增强了可读性，之后 trim() 方法的调用移除了起始空白行。
    console.log(html);
    /*
    如果你愿意的话，也可以在模板字面量中使用 \n 来指示新行的插入位置：

    let message = `Multiline\nstring`;

    console.log(message);           // "Multiline
                                    //  string"
    console.log(message.length);    // 16
     */
    console.groupEnd();

    //=============================================1.4.3. 字符串置换（Making Substitutions）=============================
    /*
    在这里，模板字面量看上去像是普通 JavaScript 字符串的升级版。两者之间的真正区别在于前者包含的置换操作。
    置换允许你将 JavaScript 表达式嵌入到模板字面量中并将其结果作为输出字符串中的一部分。

    置换部分由 ${ 和 } 包含，其中可以放入任意 JavaScript 表达式。最简单的置换是将本地变量直接嵌入到结果字符串中，例如：
     */
    console.group('--------------------1.4.3. 字符串置换（Making Substitutions）-------------------');
    let name = "Nicholas",
        m = `Hello, ${name}.`;

    console.log(m);       // "Hello, Nicholas."
    console.groupEnd();
    // 置换 ${name} 会访问本地变量 name 并将其值插入到 message 字符串中。message 变量会立即持有置换结果。
    // 模板字面量可以访问作用域中定义的任何变量。若变量未定义，在严格和非严格模式下都会抛出错误。

    console.group('-------------------置换的对象都是 JavaScript 表达式,嵌入任意运算，函数调用，等等---------------');
    let count = 10,
        price = 0.25,
        mm = `${count} items cost $${(count * price).toFixed(2)}.`;

    console.log(mm);       // "10 items cost $2.50."
    console.groupEnd();
    // 模板字面量本身也是 JavaScript 表达式，意味着你可以将模板字面量放入到另一个模板字面量内部，如下所示：
    console.group('-----------------模板字面量嵌套模板字面量---------------------------------');
    let myname = "Nicholas",
        mmm = `Hello, ${
            `my name is ${myname}`
            }.`;
    console.log(mmm);        // "Hello, my name is Nicholas."
    console.groupEnd();
    //==============================================1.4.4. 模板标签（Tagged Templates）==================================
    console.group('----------------------1.4.4. 模板标签（Tagged Templates）--------------------------');
    /*
    现在你已见识过模板字面量如何创建多行字符串，
    以及它不需要连接（concatenation）即可将值插入到字符串中。
    不过模板字面量真正的强大之处来源于模板标签。
    一个模板标签可以被转换为模板字面量并作为最终值返回。
    标签在模板的头部，即左 ` 字符之前指定，如下所示：
     */
    //let message = tag`Hello world`;
    // 本例中，tag 即模板标签，并可被转换为 `Hello world` 模板字面量。


    //定义标签（Defining Tags）
    /*
    一个标签仅代表一个函数，它接收需要处理的模板字面量。
    标签分别接收模板字面量中的片段，且必须将它们组合以得出结果。
    函数的首个参数为包含普通 JavaScript 字符串的数组。
    余下的参数为每次置换的对应值。

    标签函数一般使用剩余参数来定义，以便轻松地处理数据。如下：

    function tag(literals, ...substitutions) {
        // 返回一个字符串
    }
     */

    function passthru(literals, ...substitutions) {

        console.log('literals:', literals);
        console.log('substitutions:', substitutions);

        let result = "";

        // 只根据 substitution 的数目来运行循环
        for (let i = 0; i < substitutions.length; i++) {
            result += literals[i];
            result += substitutions[i];
        }

        // 添加最后一个 literal
        result += literals[literals.length - 1];
        console.log('passthru执行结果:',result);

        return result;
    }

    let c = 10,
        p = 0.25,
        me = passthru`${c} items cost $ ${(c * p).toFixed(2)}.`;
    console.log(me);

    console.groupEnd();

    // 在模板字面量中使用原始值（Using Raw Values in Template Literals）
    console.group('-------------------在模板字面量中使用原始值（Using Raw Values in Template Literals）------------------');
    /*
    模板标签也可以访问字符串的原始信息，主要是它可以在转义字符生效前访问它，而最简单的方式是使用内置的 String.raw() 标签。如下所示：
     */
    let message1 = `Multiline\nstring`,
        message2 = String.raw`Multiline\nstring`;

    console.log(message1);          // "Multiline
                                    //  string"
    console.log(message2);          // "Multiline\\nstring"
    console.groupEnd();
    /*
    该段代码中，message1 中的 \n 被解释为新行，而 message2 返回了 \n 的原始形式 "\n"（斜杠与字符 n）。
    类似于该种提取字符串原始信息的行为可以在必要时做更复杂的处理。
    字符串的原始信息同样会被传递给模板标签。
    标签函数中的首个参数为包含额外属性 raw 的数组。
    raw 属性是含有每个字面量值的对应原始值的数组。
    例如，literals[0] 总是等同于它的原始值 literals.raw[0]。
    了解这些之后，你可以使用如下的代码来模仿默认的 String.raw()：
     */
    console.group('----------------使用模板标签，模仿默认的 String.raw()--------------------');
    function raw(literals, ...substitutions) {
        let result = "";

        // 只根据 substitution 的数目来运行循环
        for (let i = 0; i < substitutions.length; i++) {
            result += literals.raw[i];      // use raw values instead
            result += substitutions[i];
        }

        // 添加最后一个 literal
        result += literals.raw[literals.length - 1];

        return result;
    }

    let message3 = raw`Multiline\nstring`;

    console.log(message3);           // "Multiline\\nstring"
    console.log(message3.length);    // 17
    console.groupEnd();
    console.groupEnd();
    /*
    这里并非使用 literals 而是 literals.raw 来输出结果字符串。
    这意味着包括 Unicode 代码点在内的任何转义字符都会以原始的形式返回。
    当你想在输出的字符串中包含转义字符时原始字符串非常好用
    （例如，如果你想要生成包含代码的文档，那么你期待的是输出实际代码而不是产生的效果）。
     */
    /**
     * 1.5. 总结（Summary）
     完整的 Unicode 支持允许 JavaScript 以合理的方式处理 UTF-16 字符。codePointAt() 和 String.fromCodePoint() 拥有的在代码点和字符之间的转换能力是字符串操作的一项重大进步。
     正则表达式新引入的 u 标志使得直接操作代码点而不是 16 位字符串变为可能，同时 normalize() 方法使得字符串之间的比较结果更为准确。

     ECMAScript 6 也提供了操作字符串的新方法，允许你更容易地确认子字符串而无需获取它在整个字符串中的位置。正则表达式也引入了许多功能。

     模板字面量是 ECMAScript 6 添加的一项重要内容，允许你创建领域特定语言（domain-specific languages, DSLs）以便让字符串的创建更加轻松。将变量直接嵌入到模板字面量中意味着开发者有一种比字符串拼接更为安全的方式来组合长字符串和变量。

     相比传统字符串，模板字面量内置的多行字符串支持是一项实用的改进，这也是前者永远也无法做到的。尽管在模板字面量中允许多行的存在，你依旧可以使用 \n 或其它字符转义序列。

     模板标签是创建 DSLs 最重要的部分。标签是接收模板字面量片段为参数的函数。你可以使用参数数据来返回恰当的字符串，其中包括字面量，原生字面量和置换值。标签根据它们来输出相应的结果。
     */


</script>
</html>
